import Foundation

// 属性相关

// MARK: - 存储属性
/// 1. 将常量或变量存储为实例的一部分
/// 2. 只能用于`类`和`结构体`
/// 3. 可以指定默认值

struct FixedLengthRange {
    var firstValue: Int
    let lenght: Int
}

class FixedLengthInfo {
    var firstValue: Int = 0
    let length: Int = 0
    
    // 类的存储属性,只能用static
    static var a: Int  = 123
}

public func propertyDemo_1() {
    // 值类型只有声明为var, 才能修改属性
    var rangeOfThreeItems = FixedLengthRange(firstValue: 0, lenght: 3)
    rangeOfThreeItems.firstValue = 6;
    
    // 引用类型使用let, 也可修改属性
    let rangeInfo = FixedLengthInfo()
    rangeInfo.firstValue = 6
}



// MARK: - 延时加载存储属性
/// 1. 必须使用`var`
/// 2. 因为常量属性在构造过程完成前必须要有初始值, 所以无法用作延时加载


class DataImporter {
    /*
    DataImporter 是一个负责将外部文件中的数据导入的类。
    这个类的初始化会消耗不少时间。
    */
    var fileName = "data.txt"
}

class DataManager {
    lazy var importer = DataImporter()
    var data: [String] = []
}

public func propertyDemo_2() {
    let manager = DataManager()
    manager.data.append("Some Data")
    manager.data.append("Some more Data")
    // 使用时才会创建这个属性
    // 要注意多线程问题, 可能回被多次初始化
    print(manager.importer.fileName);
}


// MARK: - 存储属性和实例变量


// MARK: - 计算属性
/// 1. 计算属性不直接存储值
/// 2. 而是提供一个getter和一个可选的setter, 来间接获取和设置其他属性或变量的值

struct Point {
    var x = 0.0
    var y = 0.0
}

struct Size {
    var width = 0.0
    var height = 0.0
}

struct Rect {
    var origin = Point()
    var size = Size()
    // 计算属性
    var center:  Point {
        get {
            let centerX = origin.x + size.width / 2.0
            let centerY = origin.y + size.height / 2.0
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - size.width / 2.0
            origin.y = newCenter.y - size.height / 2.0
        }
    }
    // 简化写法后
    var center2: Point {
        get {
            Point(x: origin.x + (size.width / 2),
                  y: origin.y + (size.height / 2))
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
    
    // 只读计算属性
    var volume: Double {
        return size.width * size.height
    }
    
//    static var info: Int {
//        return 100
//    }
}

public func propertyDemo_3() {
    var square = Rect(origin: Point(x: 0.0, y: 0.0),
        size: Size(width: 10.0, height: 10.0))
    let initialSquareCenter = square.center
    // initialSquareCenter 位于（5.0， 5.0）
    square.center = Point(x: 15.0, y: 15.0)
    print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
}


// MARK: - 属性观察器
/// 1. 观察和响应属性值的变化
/// 2. 每次属性被设置都已调用观察器, 即使两个事相同

class StepCounter {
    var totalSteps: Int = 0 {
        willSet {
            print("将 totalSteps 的值设置为 \(newValue)")
        }
        didSet {
            if totalSteps > oldValue {
                print("增加了 \(totalSteps - oldValue) 步")
            }
        }
    }
    
    // 计算属性, 可被重写
    class var a: Int {
        return 100
    }
}

public func propertyDemo_4() {
    let stepCounter = StepCounter()
    stepCounter.totalSteps = 200
    stepCounter.totalSteps = 360
    stepCounter.totalSteps = 896
}


// MARK: - 属性包装器
/// 1. 在管理属性`如何存储`和`定义属性`的代码之间, 添加的一个分隔层
/// 2. 例如, 属性需要线程安全性检查, 或者需要在数据库中存储他们的基本数据, 就必须给每个属性添加同样的代码
/// 3. 此时通过属性包装器编写一次代码, 就可以应用到多个属性上复用

@propertyWrapper
struct TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { number }
        set { number = min(newValue, 12) }
    }
}

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

public func propertyDemo_5() {
    var rectangle = SmallRectangle()
    print(rectangle.height)
    // 打印 "0"

    rectangle.height = 10
    print(rectangle.height)
    // 打印 "10"

    rectangle.height = 24
    print(rectangle.height)
    // 打印 "12"
}

@propertyWrapper
struct SmallNumber {
    private var maximum: Int
    private var number: Int
    
    var wrappedValue: Int {
        get { number }
        set { number = min(newValue, maximum)}
    }
    
    init() {
        maximum = 12
        number = 0
    }
    
    // 为使用了属性包装器的属性设置初始值时, 会调用测函数
    init(wrappedValue: Int) {
        maximum = 12
        number = min(wrappedValue, maximum)
    }
    
    init(wrappedValue: Int, maximum: Int) {
        self.maximum = maximum
        number = min(wrappedValue, maximum)
    }
}

struct ZeroRectangle {
    @SmallNumber var height: Int
    // 指定初始值
    @SmallNumber var width: Int = 2
}

